using UnityEngine;
using System.Collections;

public class Entity : MonoBehaviour {
	// Unique ID and the entity type.
	public int entityID, entityType;
	
	private LockPoint localLockPoint = null,
					  targetLockPoint = null;
	
	private Transform owner;

	// Called by the parent to update the lock down through
	// the hierarchy.
	public void updateLock() {
		
		// If the entity is locked (it is not root), update transformation.
		if (isLocked()) {
			GameObject a = localLockPoint.gameObject;
			GameObject b = targetLockPoint.gameObject;
			
			Transform aParent = transform;
			Transform bParent = b.transform.parent;
			
			if (aParent != null 
			&&  bParent != null) {
				Quaternion tempRot = Quaternion.LookRotation(-b.transform.forward, a.transform.up);
				aParent.transform.rotation = tempRot * Quaternion.Inverse(a.transform.localRotation);
				aParent.transform.position = aParent.transform.position + (b.transform.position - a.transform.position);
			} else {
				Debug.Log("Failed to fetch snap parents. (aParent:'" + aParent + "' or bParent:'" + bParent + "')");
			}
		}
		
		// Go through the children of the Entity and update them as well.
		for (int i = 0; i < transform.childCount; i++) {
			Entity child = transform.GetChild(i).GetComponent<Entity>();
			
			// Make sure it is an Entity.
			if (child != null) {
				child.updateLock();
			}
		}
	}

	// The function should return true if the recursion should
	// terminate.
	public bool each(System.Func<GameObject, bool> f) {
		if (f(gameObject)) return true;
		
		for (int i = 0; i < transform.childCount; i++) {
			GameObject child = transform.GetChild(i).gameObject;
			
			if (child.GetComponent<Entity>() != null) {
				if (child.GetComponent<Entity>().each(f)) return true;
			} else {
				if (f(child)) return true;
			}
		}
		
		return false;
	}
	
	// A pair of two LockPoints.
	public class Pair {
		public LockPoint local = null; 
		public LockPoint target = null;
		
		public bool isSet() {
			return local != null && target != null;	
		}
	};
	
	// Sets the local lock point. Should be a direct child of this Entity.
	public void setLocalPoint(LockPoint local) {
		if (localLockPoint != null) {
			localLockPoint.available = true;
		}
		
		localLockPoint = local;
		
		if (local != null) {
			local.available = false;
		}
	}
	
	// Sets the target lock point. Should be a direct child of the parent Entity.
	public void setTargetPoint(LockPoint target) {
		if (targetLockPoint != null) {
			targetLockPoint.available = true;
		}
		
		targetLockPoint = target;
		
		if (target != null) {
			target.available = false;
		}
	}
	
	// Returns the local lock point, or null if noone exists.
	public LockPoint getLocalPoint() {
		return localLockPoint;
	}
	
	// Returns the target lock point, or null if noone exists.
	public LockPoint getTargetPoint() {
		return targetLockPoint;
	}
	
	// Returns the pair of lock points that will be used if the 
	// lockTowards-function is called now. The function ignores
	// pairs where not both parts are available.
	public Pair findLocks(Entity other) {
		
		// Find nearest pair.
		Pair pair = new Pair();
		
		if (other != null) {
			eachLockPoint(delegate(LockPoint point) {
				LockPoint test = findNearestTo(other, point);
				
				if (test != null) {
					if (pair.local == null
					||  pair.target == null
					||  distance (point, test) < distance (pair.local, pair.target)) {
						if (point.available && test.available) {
							pair.local  = point;
							pair.target = test;
						}
					}
				}
			});
		}
		
		return pair;
	}
	
	// Sets the material of this object and all objects with renderers below 
	// it in the hierarchy.
	public void setMaterial(Material mat) {
		each(delegate(GameObject obj) {
			
			// Make sure it has a renderer.
			if (obj.GetComponent<Renderer>() != null) {
				obj.renderer.material = mat;
			}
			
			return false;
		});
	}
	
	// If this Entity is locked, the lock will be released. If no lock is
	// present, nothing will happend.
	public void releaseLock() {
		if (localLockPoint != null) {
			localLockPoint.available = true;
		}
		
		if (targetLockPoint != null) {
			targetLockPoint.available = true;
		}
		
		localLockPoint  = null;
		targetLockPoint = null;
		transform.parent = null;
	}
	
	// Attaches this entity to some other Entity in the specified
	// hierarchy.
	public bool lockTowards(Entity other) {
		// Find nearest pair.
		Pair pair = findLocks(other);
		
		if (pair.local != null
		&&  pair.target != null) {
		
			setLocalPoint(pair.local);
			setTargetPoint(pair.target);
			
			// Attach in hierarchy.
			transform.parent = pair.target.transform.parent;
			updateLock();
			
			return true;
		}
		
		return false;
	}
	
	// Returns true if this Entity is locked to another.
	public bool isLocked() {
		return !(
			transform.parent == null || 
			localLockPoint   == null || 
			targetLockPoint  == null
		);
	}
	
	// Returns the target LockPoint nearest to the specified LockPoint
	// in this Entity. The target LockPoint returned may not reside
	// directly below the specified entity but could be anywhere below
	// it in the hierarchy.
	private LockPoint findNearestTo(Entity other, LockPoint local) {
		LockPoint nearest = null;
		
		other.eachLockPoint(delegate(LockPoint point) {
			if (nearest == null 
			||  distance (point, local) < distance (nearest, local)) {
				if (point.available) {
					nearest = point;
				}
			}
		});
		
		return nearest;
	}
	
	// Returns the distance between two points.
	private float distance(LockPoint a, LockPoint b) {
		return (a.transform.position - b.transform.position).magnitude;
	}
	
	// Performes the specified action on all SnapPoints in this 
	// Entity, but not in entities below this one.
	private void eachLockPoint(System.Action<LockPoint> callback) {
		for (int i = 0; i < transform.childCount; i++) {
			Transform child = transform.GetChild(i);
			
			if (child.GetComponent<LockPoint>() != null) {
				callback(child.GetComponent<LockPoint>());
			} else if (child.GetComponent<Entity>() != null) {
				child.GetComponent<Entity>().eachLockPoint(callback);
			}
		}
	}
	
	// Sets the layer of all objects in this Entity recursively.
	public void setLayer(int layer) {
		each(delegate(GameObject obj) {
			obj.layer = layer;
			return false;
		});
	}
	
	// Returns the handle of the root component, and if no such exists, null.
	// The search is not recursive.
	public HandlePoint getHandlePoint() {
		for (int i = 0; i < transform.childCount; i++) {
			Transform child = transform.GetChild(i);
			
			if (child.GetComponent<HandlePoint>() != null) {
				return child.GetComponent<HandlePoint>();
			}
		}
		
		return null;
	}
	
	// Returns the handle of the root component, and if no such exists, null.
	// The search is not recursive.
	public BarrelPoint getBarrelPoint() {
		for (int i = 0; i < transform.childCount; i++) {
			Transform child = transform.GetChild(i);
			
			if (child.GetComponent<BarrelPoint>() != null) {
				return child.GetComponent<BarrelPoint>();
			}
		}
		
		return null;
	}
	
	// Sets the owner of this Entity. Set to null if it resides in the world.
	public void setOwner(Transform owner) {
		this.owner = owner;
	}
	
	// Returns the player that is the owner of the entity, or, if it is
	// in the world, returns null.
	public Transform getOwner() {
		return owner;
	}
}
